package com.caishi.lkx.gateway;

import cn.hutool.core.map.MapUtil;
import com.zzw.common.ResultCode;
import com.zzw.common.exception.BizRuntimeException;
import com.zzw.common.handler.ExceptionServletInvocableHandlerPipeline;
import com.zzw.common.handler.ServletInvocableHandlerPipeline;
import com.zzw.common.keray.KerayServletInvocableHandlerMethod;
import com.zzw.common.resolver.KerayHandlerMethodArgumentResolverConfig;
import com.zzw.common.utils.CommonUtil;
import com.caishi.lkx.common.CommonResultCode;
import com.caishi.lkx.common.role.RoleAuth;
import com.caishi.lkx.common.role.Roles;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author by keray
 * date:2021/4/1 4:29 下午
 */
@Slf4j
public class VipRoleServletInvocableHandlerMethod extends KerayServletInvocableHandlerMethod {

    private final RoleAuth roleAuth;


    public VipRoleServletInvocableHandlerMethod(HandlerMethod handlerMethod,
                                                ServletInvocableHandlerPipeline[] handlers,
                                                KerayHandlerMethodArgumentResolverConfig resolvers,
                                                RoleAuth roleAuth
    ) {
        super(handlerMethod, handlers, resolvers);
        this.roleAuth = roleAuth;

    }

    @Override
    public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        // 接口鉴权
        try {
            Roles roles = getMethodAnnotation(Roles.class);
            if (roles == null) {
                roles = CommonUtil.getClassAllAnnotation(this.getMethod().getDeclaringClass(), Roles.class);
            }
            ResultCode resultCode = roleAuth.apply(signMethod(this.getMethod()), roles, request.getNativeRequest(HttpServletRequest.class));
            if (resultCode.getCode() != CommonResultCode.ok.getCode()) {
                throw new BizRuntimeException(resultCode);
            }
        } catch (Exception e) {
            var ctx = new ConcurrentHashMap<>();
            ctx.put("ERROR", e);
            try {
                return work(this, args(request), request, ctx);
            } finally {
                ctx.clear();
            }
        }
        return super.invokeForRequest(request, mavContainer, providedArgs);
    }

    @Override
    public Object work(HandlerMethod handlerMethod, Object[] args, NativeWebRequest request, Map<Object, Object> workContext) throws Exception {
        Exception e = (Exception) workContext.get("ERROR");
        if (e != null) {
            // 如果前面异常  走到Exception后就返回
            var ex = pipelines[pipelineIndex];
            if (ex instanceof ExceptionServletInvocableHandlerPipeline<?> ep) {
                return ep.work(this, args, request, workContext, () -> {
                    throw e;
                });
            }
        }
        if (pipelineIndex == pipelines.length) {
            return doInvoke(args);
        }
        return pipelines[pipelineIndex++].work(this, args, request, workContext, this);
    }


    private String signMethod(Method method) {
        return String.format("%s#%s(%s)", method.getDeclaringClass().getSimpleName(), method.getName(),
                Arrays.stream(method.getParameterTypes()).map(Class::getSimpleName).collect(Collectors.joining(",")));
    }

    private Object[] args(NativeWebRequest request) throws IOException {
        var req = request.getNativeRequest(HttpServletRequest.class);
        var res = new Object[this.getMethodParameters().length];
        if (req == null || res.length == 0) return res;
        Object body = null;
        if (!"GET".equals(req.getMethod())) {
            var in = req.getInputStream();
            var bs = new byte[in.available()];
            in.read(bs);
            body = new String(bs);
        }
        res[0] = MapUtil.builder()
                .put("params", new HashMap<>() {{
                    putAll(req.getParameterMap());
                }})
                .put("body", body)
                .build();
        return res;
    }

}
